// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/bybit_futures/order_v2/parser.h"

#include <cmath>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "coin2/base/conversion.h"
#include "coin2/base/log.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::bybit_futures::order_v2 {

namespace {

using coin::proto::AccountBalance;
using coin::proto::AccountPosition;
using coin::proto::CancelOrderResponse;
using coin::proto::FillType;
using coin::proto::ProductFillBundle;
using coin::proto::ProductOrderBundle;
using coin::proto::ProductOrderElement;
using coin::proto::ProductPosition;
using coin::proto::SubmitOrderResponse;
using coin2::exchange::base::order::IOrderContext;
using coin2::exchange::bybit_futures::symbology::BybitFuturesCurrency;
using coin2::exchange::bybit_futures::symbology::BybitFuturesProduct;

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;

inline int64_t GetProcOrderId(const JsonValue& data) {
  int64_t proc_order_id = 0;
  if (data.HasMember("order_link_id")) {
    std::string link_order_str(data["order_link_id"].GetString());
    if (!link_order_str.empty()) {
      proc_order_id = stoll(link_order_str);
    }
  }
  return proc_order_id;
}

inline coin::proto::OrderSide GetOrderSide(const std::string& side) {
  if (side == "Buy") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "Sell") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

inline coin::proto::OrderType GetOrderType(const std::string& type) {
  if (type == "Limit") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "Market") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void SetOrderStatus(const std::string& status, ProductOrderElement* order) {
  if (status == "Created" || status == "Rejected" || status == "PendingCancel") {
    order->set_is_live(false);
  } else if (status == "New" || status == "PartiallyFilled") {
    order->set_is_live(true);
  } else if (status == "Cancelled") {
    order->set_cancel_confirmed(true);
  } else if (status == "Filled") {
    order->set_fully_filled(true);
  } else {
    LOG(ERROR) << "[ParseRestOrderInfo] Unknown order status! " << status;
  }
}

inline coin::proto::OrderErrorCode GetOrderErrorCodeFromBybitFutures(
    const JsonValue& json_error_msg) {
  using coin::proto::OrderErrorCode;
  auto result = coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR;
  std::string_view error_code(json_error_msg.GetString());

  if (error_code == "0") {
    result = OrderErrorCode::ORDER_NO_ERROR;
  } else if (error_code == "10006") {
    result = OrderErrorCode::API_LIMIT_ERROR;
  } else if (error_code == "20071") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "30010") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "30021") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "30031") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "30049") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "30067") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "20001") {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  } else if (error_code == "30032") {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  } else if (error_code == "30034") {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  } else if (error_code == "30037") {
    result = OrderErrorCode::ORDER_CANCELED_ALREADY;
  }

  return result;
}

bool IsBybitFuturesErrMessage(const JsonValue& doc) {
  return doc.HasMember("ret_code") && std::string_view(doc["ret_code"].GetString()) != "0";
}

bool IsOrderUpdate(const JsonValue& json) {
  if (!json.HasMember("topic")) {
    return false;
  }
  std::string_view topic(json["topic"].GetString());
  return topic == "position" || topic == "order" || topic == "execution";
}

}  // namespace

void BybitFuturesParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance BybitFuturesParser::ParseBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Bybit");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    // only parse USDT for linear
    if (is_linear() && native_currency != "USDT") {
      continue;
    }
    // ignore USDT for non-linear
    if (!is_linear() && native_currency == "USDT") {
      continue;
    }
    double total = raw_balance_map.count(native_currency) == 0
                       ? 0.
                       : raw_balance_map.at(native_currency).total();
    if (!CompareLastBalance(native_currency, total, doc)) {
      continue;
    }
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool BybitFuturesParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  {
    "ret_code": 0,
    "ret_msg": "OK",
    "ext_code": "",
    "ext_info": "",
    "result": {
        "BTC": {
            "equity": 1002,                         //equity = wallet_balance + unrealised_pnl
            "available_balance": 999.99987471,      //available_balance
            "used_margin": 0.00012529,              // = wallet_balance - available_balance
            "order_margin": 0.00012529,             //Used margin by order
            "position_margin": 0,                   //position margin
            "occ_closing_fee": 0,                   //position closing fee
            "occ_funding_fee": 0,                   //funding fee
            "wallet_balance": 1000,                 //wallet balance
            "realised_pnl": 0,                      //daily realized profit and loss
            "unrealised_pnl": 2,                    //unrealised profit and loss
            "cum_realised_pnl": 0,                  //total relised profit and loss
            "given_cash": 0,                        //given_cash
            "service_cash": 0                       //service_cash
        }
    },
    "time_now": "1578284274.816029",
    "rate_limit_status": 98,
    "rate_limit_reset_ms": 1580885703683,
    "rate_limit": 100
  }
  */
  if (!doc.IsObject() || IsBybitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseRawBalance] " << JsonToString(doc);
    return false;
  }
  if (!doc.HasMember("result") || !doc["result"].IsObject()) {
    LOG(ERROR) << "[ParseRawBalance] invalid json. " << JsonToString(doc);
    return false;
  }

  for (const auto& info : doc["result"].GetObject()) {
    const auto& data = info.value;
    if (!data.HasMember("wallet_balance") || !data.HasMember("unrealised_pnl") ||
        !data.HasMember("available_balance")) {
      continue;
    }
    double total =
        stod(data["wallet_balance"].GetString()) + stod(data["unrealised_pnl"].GetString());
    double avail = stod(data["available_balance"].GetString());

    CurrencyBalance balance;
    balance.set_currency_native(info.name.GetString());
    balance.set_total(total);
    balance.set_hold(total - avail);
    balance.set_available(avail);
    raw_balance_map->emplace(balance.currency_native(), balance);
  }

  return true;
}

AccountBalance BybitFuturesParser::ParseBalanceFromWs(const JsonValue& doc) {
  /*
  {
    "topic": "position",
    "action": "update",
    "data": [
        {
            "user_id":  1,                            // user ID
            "symbol": "BTCUSD",                       // the contract for this position
            "size": 11,                               // the current position amount
            "side": "Sell",                           // side
            "position_value": "0.00159252",           // positional value
            "entry_price": "6907.291588174717",       // entry price
            "liq_price": "7100.234",                  // liquidation price
            "bust_price": "7088.1234",                // bankruptcy price
            "leverage": "1",                           // leverage
            "order_margin":  "1",                      // order margin
            "position_margin":  "1",                   // position margin
            "available_balance":  "2",                 // available balance
            "take_profit": "0",                        // take profit price
            "tp_trigger_by":  "LastPrice",             // take profit trigger price
            "stop_loss": "0",                          // stop loss price
            "sl_trigger_by":  "",                     // stop loss trigger price
            "realised_pnl":  "0.10",               // realised PNL
            "trailing_stop": "0",                  // trailing stop points
            "trailing_active": "0",                // trailing stop trigger price
            "wallet_balance":  "4.12",             // wallet balance
            "risk_id":  1,
            "occ_closing_fee":  "0.1",             // position closing
            "occ_funding_fee":  "0.1",             // funding fee
            "auto_add_margin": 0,                  // auto margin replenishment switch
            "cum_realised_pnl":  "0.12",           // Total realized profit and loss
            "position_status": "Normal",           // status of position
            "position_seq": 14                     // position version number
        }
    ]
  }
  */
  AccountBalance acc_balance;
  if (!(doc.HasMember("data") && doc["data"].IsArray())) {
    LOG(ERROR) << "[ParseBalanceFromWs] " << JsonToString(doc);
    return acc_balance;
  }

  acc_balance.set_exchange("Bybit");
  acc_balance.set_market_type("Futures");
  for (const auto& data : doc["data"].GetArray()) {
    // balance:
    //   total - wallet_balance
    //   avail - available_balance
    //   hold  - order_margin
    if (!symcache_->has_currency_native(data["symbol"].GetString())) {
      continue;
    }
    if (!data.HasMember("wallet_balance") || !data.HasMember("available_balance")) {
      continue;
    }
    double total = stod(data["wallet_balance"].GetString());
    double avail = stod(data["available_balance"].GetString());
    double hold = total - avail;

    auto* currency = symcache_->GetCurrencyByNative(data["symbol"].GetString());
    auto* currency_balance = acc_balance.add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    currency_balance->set_available(avail);
    currency_balance->set_hold(hold);
    currency_balance->set_total(total);
  }

  return acc_balance;
}

ProductPosition BybitFuturesParser::ParseProductPosition(const JsonValue& data) {
  ProductPosition position;
  if (!data.HasMember("symbol") || !data.HasMember("side") || !data.HasMember("size")) {
    LOG(ERROR) << "Unexpected position data! " << JsonToString(data);
    return position;
  }

  auto symbol_str = data["symbol"].GetString();
  if (!symcache_->has_product_native(symbol_str)) {
    LOG(ERROR) << "[ParseProductPosition] ignored unknown product: " << symbol_str;
    return position;
  }
  auto* product = symcache_->GetProductByNative(symbol_str);
  if (!product) {
    LOG(ERROR) << "[ParseProductPosition] ignored unknown product: " << symbol_str;
    return position;
  }

  position.set_symbol(product->symbol());
  position.set_native_symbol(product->order_native_symbol());
  std::string_view side(data["side"].GetString());
  double pos_net = stod(data["size"].GetString());
  if (side == "Buy") {
    position.set_long_position(pos_net);
    position.set_net_position(pos_net);
  } else if (side == "Sell") {
    position.set_short_position(-pos_net);
    position.set_net_position(-pos_net);
  } else if (side == "None" && pos_net == 0) {
    position.set_short_position(0);
    position.set_long_position(0);
    position.set_net_position(0);
  } else {
    LOG(ERROR) << "[ParsePosition] Unexpected side! " << JsonToString(data);
  }
  return position;
}

bool BybitFuturesParser::ParseProductPositionV2(
    const JsonValue& data,
    RawPositionMap* raw_position_map) {
  if (!data.HasMember("symbol") || !data.HasMember("side") || !data.HasMember("size")) {
    LOG(ERROR) << "Unexpected position data! " << JsonToString(data);
    return false;
  }
  std::string_view side(data["side"].GetString());
  double pos_net = stod(data["size"].GetString());
  if (side != "Buy" && side != "Sell" && !(side == "None" && pos_net == 0)) {
    LOG(ERROR) << "[ParsePosition] Unexpected side! " << JsonToString(data);
    return false;
  }

  auto native_symbol = data["symbol"].GetString();
  if (raw_position_map->count(native_symbol) == 0) {
    raw_position_map->emplace(native_symbol, ProductPosition{});
  }
  auto& position = raw_position_map->at(native_symbol);

  if (side == "Buy") {
    position.set_long_position(pos_net);
    position.set_net_position(pos_net);
  } else if (side == "Sell") {
    position.set_short_position(-pos_net);
    position.set_net_position(-pos_net);
  } else if (side == "None" && pos_net == 0) {
    position.set_short_position(0);
    position.set_long_position(0);
    position.set_net_position(0);
  } else {
    NOTREACHED() << "Unexpected position data! " << JsonToString(data);
  }

  return true;
}

void BybitFuturesParser::ParseRestAccountPosition() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountPosition] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  ignorable_position_event_ = false;
  auto account_position = ParsePosition(document);
  if (ignorable_position_event_) {
    result_.set_type(MsgType::IGNORABLE);
    return;
  }
  if (!account_position.has_exchange() || !account_position.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  result_.set_type(MsgType::ACCOUNT_POSITION);
  *result_.mutable_account_position() = std::move(account_position);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountPosition BybitFuturesParser::ParsePosition(const JsonValue& doc) {
  RawPositionMap raw_position_map;
  auto success = ParseRawPosition(doc, &raw_position_map);
  if (!success) {
    return {};
  }

  if (raw_position_map.empty()) {
    // TODO(daniel): set 0 position for specify symbol
    LOG(ERROR) << "@OG team, please handle this case that empty http response";
  }
  // update position cache
  for (const auto& [native_symbol, position] : raw_position_map) {
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    position_cache_map_[native_symbol] = position;
  }

  if (position_cache_map_.size() != symcache_->GetProductNative().size()) {
    // not ready for all symbols
    ignorable_position_event_ = true;
    return {};
  }

  AccountPosition acc_position;
  acc_position.set_exchange("Bybit");
  acc_position.set_market_type("Futures");
  if (is_account_position_initialized_) {
    // update position for single rest query
    for (const auto& [native_symbol, pos] : raw_position_map) {
      if (!symcache_->has_product_native(native_symbol)) {
        continue;
      }
      auto* position = acc_position.add_each_position();
      position->MergeFrom(pos);
      const auto* product = symcache_->GetProductByNative(native_symbol);
      position->set_symbol(product->symbol());
      position->set_native_symbol(product->order_native_symbol());
      DLOG(INFO) << "POS: " << product->symbol() << " " << position->net_position();
    }
  } else {
    // update position for all symbols
    for (const auto& native_symbol : symcache_->GetProductNative()) {
      auto* position = acc_position.add_each_position();
      if (position_cache_map_.count(native_symbol) == 0) {
        position->set_long_position(0);
        position->set_short_position(0);
        position->set_net_position(0);
        position->set_available_long_position(0);
        position->set_available_short_position(0);
      } else {
        position->MergeFrom(position_cache_map_.at(native_symbol));
      }
      const auto* product = symcache_->GetProductByNative(native_symbol);
      position->set_symbol(product->symbol());
      position->set_native_symbol(product->order_native_symbol());
      DLOG(INFO) << "POS: " << product->symbol() << " " << position->net_position();
    }
    is_account_position_initialized_ = true;
  }

  return acc_position;
}

bool BybitFuturesParser::ParseRawPosition(const JsonValue& doc, RawPositionMap* raw_position_map) {
  if (is_linear()) {
    return ParseLinearRawPosition(doc, raw_position_map);
  } else {
    return ParseNormalRawPosition(doc, raw_position_map);
  }
}

bool BybitFuturesParser::ParseNormalRawPosition(
    const JsonValue& doc,
    RawPositionMap* raw_position_map) {
  /*
  {
    "ret_code": 0,
    "ret_msg": "OK",
    "ext_code": "",
    "ext_info": "",
    "result": {
        "id": 27913,
        "user_id": 1,
        "risk_id": 1,
        "symbol": "BTCUSD",
        "side": "Buy",
        "size": 5,
        "position_value": "0.0006947",
        "entry_price": "7197.35137469",
        "auto_add_margin": 0,
        "leverage": "1",
        "effective_leverage": "1",
        "position_margin": "0.0006947",
        "liq_price": "3608",
        "bust_price": "3599",
        "occ_closing_fee": "0.00000105",
        "occ_funding_fee": "0",
        "take_profit": "0",
        "stop_loss": "0",
        "trailing_stop": "0",
        "position_status": "Normal",
        "deleverage_indicator": 4,
        "oc_calc_data":
  "{\"blq\":2,\"blv\":\"0.0002941\",\"slq\":0,\"bmp\":6800.408,\"smp\":0,\"fq\":-5,\"fc\":-0.00029477,\"bv2c\":1.00225,\"sv2c\":1.0007575}",
        "order_margin": "0.00029477",
        "wallet_balance": "0.03000227",
        "realised_pnl": "-0.00000126",
        "unrealised_pnl": 0,
        "cum_realised_pnl": "-0.00001306",
        "cross_seq": 444081383,
        "position_seq": 287141589,
        "created_at": "2019-10-19T17:04:55Z",
        "updated_at": "2019-12-27T20:25:45.158767Z"
    },
    "time_now": "1577480599.097287",
    "rate_limit_status": 119,
    "rate_limit_reset_ms": 1580885703683,
    "rate_limit": 120
  }
  {
    "ret_code": 0,
    "ret_msg": "OK",
    "ext_code": "",
    "ext_info": "",
    "result": [
      {
        "data": {
          "id": 0,
          "position_idx": 0,
          "mode": 0,
          "user_id": 600208,
          "risk_id": 81,
          "symbol": "BTCUSDU21",
          "side": "None",
          "size": 0,
          "position_value": "0",
          "entry_price": "0",
          "is_isolated": false,
          "auto_add_margin": 1,
          "leverage": "100",
          "effective_leverage": "0",
          "position_margin": "0",
          "liq_price": "0",
          "bust_price": "0",
          "occ_closing_fee": "0",
          "occ_funding_fee": "0",
          "take_profit": "0",
          "stop_loss": "0",
          "trailing_stop": "0",
          "position_status": "Normal",
          "deleverage_indicator": 0,
          "oc_calc_data":
            "{\"blq\":0,\"slq\":0,\"bmp\":0,\"smp\":0,\"bv2c\":0.0115075,\"sv2c\":0.0114925}",
          "order_margin": "0",
          "wallet_balance": "0.00000045",
          "realised_pnl": "0",
          "unrealised_pnl": 0,
          "cum_realised_pnl": "0",
          "cross_seq": -1,
          "position_seq": 0,
          "created_at": "2021-04-25T07:55:40.786622213Z",
          "updated_at": "2021-05-07T13:05:47.283558571Z",
          "tp_sl_mode": "Full"
        },
        "is_valid": true
      }
    ],
    "time_now": "1624938127.228709",
    "rate_limit_status": 119,
    "rate_limit_reset_ms": 1624938127226,
    "rate_limit": 120
  }
  */
  if (!doc.IsObject() || IsBybitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return false;
  }
  if (!doc.HasMember("result")) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return false;
  }

  if (doc["result"].IsArray()) {
    for (const auto& info : doc["result"].GetArray()) {
      const auto& data = info.HasMember("data") ? info["data"] : info;
      if (!ParseProductPositionV2(data, raw_position_map)) {
        return false;
      }
    }
    return true;
  } else {
    const auto& info = doc["result"];
    const auto& data = info.HasMember("data") ? info["data"] : info;
    return ParseProductPositionV2(data, raw_position_map);
  }
}

bool BybitFuturesParser::ParseLinearRawPosition(
    const JsonValue& doc,
    RawPositionMap* raw_position_map) {
  /*
  {
    "ret_code": 0,
    "ret_msg": "OK",
    "ext_code": "",
    "ext_info": "",
    "result": [{
      "user_id": 600226,
      "symbol": "BTCUSDT",
      "side": "Buy",
      "size": 0,
      "position_value": 0,
      "entry_price": 0,
      "liq_price": 0,
      "bust_price": 0,
      "leverage": 100,
      "auto_add_margin": 0,
      "is_isolated": false,
      "position_margin": 0,
      "occ_closing_fee": 0,
      "realised_pnl": 0,
      "cum_realised_pnl": 0,
      "free_qty": 0,
      "tp_sl_mode": "Full",
      "unrealised_pnl": 0,
      "deleverage_indicator": 0
    }, {
      "user_id": 600226,
      "symbol": "BTCUSDT",
      "side": "Sell",
      "size": 0,
      "position_value": 0,
      "entry_price": 0,
      "liq_price": 0,
      "bust_price": 0,
      "leverage": 100,
      "auto_add_margin": 0,
      "is_isolated": false,
      "position_margin": 0,
      "occ_closing_fee": 0,
      "realised_pnl": 0,
      "cum_realised_pnl": 0,
      "free_qty": 0,
      "tp_sl_mode": "Full",
      "unrealised_pnl": 0,
      "deleverage_indicator": 0
    }],
    "time_now": "1608704227.786672",
    "rate_limit_status": 119,
    "rate_limit_reset_ms": 1608704227783,
    "rate_limit": 120
  }
  */
  if (!doc.IsObject() || IsBybitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseLinearPosition] " << JsonToString(doc);
    return false;
  }
  if (!doc.HasMember("result") || !doc["result"].IsArray()) {
    LOG(ERROR) << "[ParseLinearPosition] " << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["result"].GetArray()) {
    if (!data.HasMember("symbol") || !data.HasMember("side") || !data.HasMember("size")) {
      LOG(ERROR) << "Unexpected position data! " << JsonToString(data);
      return false;
    }
    std::string_view side(data["side"].GetString());
    if (side != "Buy" && side != "Sell") {
      LOG(ERROR) << "[ParseLinearPosition] Unexpected side! " << JsonToString(data);
      return false;
    }

    auto native_symbol = data["symbol"].GetString();
    if (side == "Buy") {
      buy_pos_map_[native_symbol] = std::abs(stod(data["size"].GetString()));
      buy_free_pos_map_[native_symbol] = std::abs(stod(data["free_qty"].GetString()));
    } else if (side == "Sell") {
      sell_pos_map_[native_symbol] = std::abs(stod(data["size"].GetString()));
      sell_free_pos_map_[native_symbol] = std::abs(stod(data["free_qty"].GetString()));

      if (raw_position_map->count(native_symbol) == 0) {
        raw_position_map->emplace(native_symbol, ProductPosition{});
      }
      auto& position = raw_position_map->at(native_symbol);

      position.set_long_position(buy_pos_map_[native_symbol]);
      position.set_short_position(-sell_pos_map_[native_symbol]);
      position.set_net_position(buy_pos_map_[native_symbol] - sell_pos_map_[native_symbol]);
      position.set_available_long_position(buy_free_pos_map_[native_symbol]);
      position.set_available_short_position(-sell_free_pos_map_[native_symbol]);
      DLOG(INFO) << native_symbol << " Pos: " << buy_pos_map_[native_symbol] << " "
                 << sell_pos_map_[native_symbol];
    } else {
      NOTREACHED() << "[ParseLinearPosition] Unexpected side! " << JsonToString(data);
    }
  }

  return true;
}

AccountPosition BybitFuturesParser::ParseNormalPositionFromWs(const JsonValue& doc) {
  // Same format as ParseBalanceFromWs
  AccountPosition acc_position;
  if (!(doc.HasMember("data") && doc["data"].IsArray())) {
    LOG(ERROR) << "[ParseNormalPositionFromWs] " << JsonToString(doc);
    return acc_position;
  }

  acc_position.set_exchange("Bybit");
  acc_position.set_market_type("Futures");
  for (const auto& data : doc["data"].GetArray()) {
    auto* position = acc_position.add_each_position();
    auto new_position = ParseProductPosition(data);
    position->MergeFrom(new_position);
  }
  return acc_position;
}

AccountPosition BybitFuturesParser::ParseLinearPositionFromWs(const JsonValue& doc) {
  /*
  {
    "topic": "position",
    "action": "update",
    "data": [{
      "user_id": "600226",
      "symbol": "BTCUSDT",
      "size": "0.012",
      "side": "Sell",
      "position_value": "273.8985",
      "entry_price": "22824.875",
      "liq_price": "999999",
      "bust_price": "999999",
      "leverage": "100",
      "order_margin": "0.52869487",
      "position_margin": "691.43994",
      "occ_closing_fee": "8.999991",
      "take_profit": "0",
      "tp_trigger_by": "UNKNOWN",
      "stop_loss": "0",
      "sl_trigger_by": "UNKNOWN",
      "trailing_stop": "0",
      "realised_pnl": "0.06847458",
      "auto_add_margin": "0",
      "cum_realised_pnl": "0.06847458",
      "position_status": "Normal",
      "position_id": "0",
      "position_seq": "3073",
      "adl_rank_indicator": "0",
      "free_qty": "0.012",
      "tp_sl_mode": "UNKNOWN"
    }, {
      "user_id": "600226",
      "symbol": "BTCUSDT",
      "size": "0.017",
      "side": "Buy",
      "position_value": "389.432",
      "entry_price": "22907.766",
      "liq_price": "1",
      "bust_price": "1",
      "leverage": "100",
      "order_margin": "0",
      "position_margin": "690.42847",
      "occ_closing_fee": "0.00000375",
      "take_profit": "0",
      "tp_trigger_by": "UNKNOWN",
      "stop_loss": "0",
      "sl_trigger_by": "UNKNOWN",
      "trailing_stop": "0",
      "realised_pnl": "0.09735798",
      "auto_add_margin": "0",
      "cum_realised_pnl": "0.09735798",
      "position_status": "Normal",
      "position_id": "0",
      "position_seq": "3074",
      "adl_rank_indicator": "3",
      "free_qty": "0.017",
      "tp_sl_mode": "UNKNOWN"
    }]
  }
  */
  AccountPosition acc_position;
  if (!doc.IsObject() || IsBybitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseLinearPositionFromWs] " << JsonToString(doc);
    return acc_position;
  }

  // 20210713(daniel): bybit adjusted the behaviours of position push,
  // only updated long/short position will be pushed.
  std::set<std::string> symbols;
  for (const auto& data : doc["data"].GetArray()) {
    if (!data.HasMember("symbol") || !data.HasMember("side") || !data.HasMember("size")) {
      LOG(ERROR) << "Unexpected position data! " << JsonToString(data);
      return acc_position;
    }
    std::string symbol = data["symbol"].GetString();
    if (!symcache_->has_product_native(symbol)) {
      continue;
    }
    if (buy_pos_map_.count(symbol) == 0 || sell_pos_map_.count(symbol) == 0) {
      // position is not ready. need rest query first
      continue;
    }
    std::string_view side(data["side"].GetString());
    if (side == "Buy") {
      symbols.emplace(symbol);
      buy_pos_map_[symbol] = std::abs(stod(data["size"].GetString()));
      buy_free_pos_map_[symbol] = std::abs(stod(data["free_qty"].GetString()));
    } else if (side == "Sell") {
      symbols.emplace(symbol);
      sell_pos_map_[symbol] = std::abs(stod(data["size"].GetString()));
      sell_free_pos_map_[symbol] = std::abs(stod(data["free_qty"].GetString()));
    } else {
      LOG(ERROR) << "[ParseLinearPositionFromWs] Unexpected side! " << JsonToString(data);
    }
  }

  acc_position.set_exchange("Bybit");
  acc_position.set_market_type("Futures");
  for (auto& symbol : symbols) {
    if (buy_pos_map_.count(symbol) == 0) {
      continue;
    }
    if (sell_pos_map_.count(symbol) == 0) {
      continue;
    }
    auto* product = symcache_->GetProductByNative(symbol);
    if (!product) {
      continue;
    }

    auto* position = acc_position.add_each_position();
    position->set_symbol(product->symbol());
    position->set_native_symbol(product->order_native_symbol());

    position->set_long_position(buy_pos_map_[symbol]);
    position->set_short_position(-sell_pos_map_[symbol]);
    position->set_net_position(buy_pos_map_[symbol] - sell_pos_map_[symbol]);
    position->set_available_long_position(buy_free_pos_map_[symbol]);
    position->set_available_short_position(-sell_free_pos_map_[symbol]);
    DLOG(INFO) << symbol << " ws Pos: " << buy_pos_map_[symbol] << " " << sell_pos_map_[symbol];
  }

  return acc_position;
}

ProductOrderElement BybitFuturesParser::ParseRestOrderInfo(const JsonValue& data) {
  ProductOrderElement order;
  order.set_market_type(coin::proto::MarketType::Futures);
  order.set_exchange_type(coin::proto::ExchangeType::Bybit);
  order.set_api_version("v2");
  order.set_exchange_order_id(data["order_id"].GetString());
  order.set_symbol(data["symbol"].GetString());
  order.set_price(stod(data["price"].GetString()));
  order.set_qty(stod(data["qty"].GetString()));
  order.set_proc_order_id(GetProcOrderId(data));
  order.set_side(GetOrderSide(data["side"].GetString()));
  order.set_order_type(GetOrderType(data["order_type"].GetString()));
  order.set_timestamp(msg_->timestamp);
  SetOrderStatus(data["order_status"].GetString(), &order);
  return order;
}

void BybitFuturesParser::ParseRestOrdersInfo() {
  /*
  {
    "ret_code": 0,
    "ret_msg": "OK",
    "ext_code": "",
    "ext_info": "",
    "result": [{
      "user_id": 600226,
      "symbol": "BTCUSD",
      "side": "Sell",
      "order_type": "Limit",
      "price": "23734",
      "qty": 1,
      "time_in_force": "PostOnly",
      "order_status": "New",
      "ext_fields": {
        "o_req_num": -3066112512035
      },
      "leaves_qty": 1,
      "leaves_value": "0.00004213",
      "cum_exec_qty": 0,
      "cum_exec_value": null,
      "cum_exec_fee": null,
      "reject_reason": "EC_NoError",
      "cancel_type": "UNKNOWN",
      "order_link_id": "1608699931448909447",
      "created_at": "2020-12-23T05:05:37.332062Z",
      "updated_at": "2020-12-23T05:05:37.332278Z",
      "order_id": "0cd8d008-c113-446f-a574-1050c7ed74ee"
    }],
    "time_now": "1608699937.546946",
    "rate_limit_status": 597,
    "rate_limit_reset_ms": 1608699937545,
    "rate_limit": 600
  }
  */
  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestOrdersInfo] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!data.IsObject() || IsBybitFuturesErrMessage(data)) {
    LOG(ERROR) << "[ParseRestOrdersInfo] " << JsonToString(data);
    return;
  }

  if (!data.HasMember("result") || !data["result"].IsArray()) {
    LOG(ERROR) << "[ParseRestOrdersInfo] Unexpected rest order list format! " << JsonToString(data);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& info : data["result"].GetArray()) {
    auto* order = product_order_bundle->add_each_order();
    auto new_order = ParseRestOrderInfo(info);
    order->MergeFrom(new_order);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

ProductOrderBundle BybitFuturesParser::ParseWsOrderInfoListV2(const JsonValue& order_data) {
  /*
  {
    "topic": "order",
    "data": [
        {
            "order_id": "xxxxxxxx-xxxx-xxxx-9a8f-4a973eb5c418",
            "order_link_id": "",
            "symbol": "BTCUSD",
            "side": "Sell",
            "order_type": "Market",
            "price": "8579.5",
            "qty": 1,
            "time_in_force": "ImmediateOrCancel",
            "create_type": "CreateByClosing",
            "cancel_type": "",
            "order_status": "Filled",
            "leaves_qty": 0,
            "cum_exec_qty": 1,
            "cum_exec_value": "0.00011655",
            "cum_exec_fee": "0.00000009",
            "timestamp": "2020-01-14T14:09:31.778Z",
            "take_profit": "0",
            "stop_loss": "0",
            "trailing_stop": "0",
            "trailing_active": "0",
            "last_exec_price": "8300"
        }
    ]
  }
  */
  ProductOrderBundle product_order_bundle;
  if (!order_data.IsObject() || IsBybitFuturesErrMessage(order_data)) {
    LOG(ERROR) << "[ParseWsOrderInfoList] " << JsonToString(order_data);
    return product_order_bundle;
  }

  if (!(order_data.HasMember("data") && order_data["data"].IsArray())) {
    LOG(ERROR) << "Unexpected ws order list format! " << JsonToString(order_data);
    return product_order_bundle;
  }

  for (const auto& data : order_data["data"].GetArray()) {
    auto* order = product_order_bundle.add_each_order();
    auto new_order = ParseRestOrderInfo(data);
    order->MergeFrom(new_order);
  }
  return product_order_bundle;
}

void BybitFuturesParser::ParseRestFillInfoList() {
  /*
  {
    "ret_code": 0,
    "ret_msg": "OK",
    "ext_code": "",
    "ext_info": "",
    "result": {
        "order_id": "Abandoned!!", // Abandoned!!
        "trade_list": [
            {
                 "closed_size": 0, // The corresponding closing size of the closing order
                 "cross_seq": 277136382,
                 "exec_fee": "0.0000001",
                 "exec_id": "256e5ef8-abfe-5772-971b-f944e15e0d68",
                 "exec_price": "8178.5",
                 "exec_qty": 1,
                 "exec_time": "1571676941.70682",    //Abandoned!!
                 "exec_type": "Trade", //Exec Type Enum
                 "exec_value": "0.00012227",
                 "fee_rate": "0.00075",
                 "last_liquidity_ind": "RemovedLiquidity", //Liquidity Enum
                 "leaves_qty": 0,
                 "nth_fill": 2,
                 "order_id": "7ad50cb1-9ad0-4f74-804b-d82a516e1029",
                 "order_link_id": "",
                 "order_price": "8178",
                 "order_qty": 1,
                 "order_type": "Market", //Order Type Enum
                 "side": "Buy", //Side Enum
                 "symbol": "BTCUSD", //Symbol Enum
                 "user_id": 1,
                 "trade_time_ms": 1577480599000
            }
        ]
    },
    "time_now": "1577483699.281488",
    "rate_limit_status": 118,
    "rate_limit_reset_ms": 1577483699244737,
    "rate_limit": 120
  }
  */
  JsonDocument order_data;
  if (!PrepareJsonDocument(&order_data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!order_data.IsObject() || IsBybitFuturesErrMessage(order_data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] " << JsonToString(order_data);
    return;
  }

  std::string field;
  if (is_linear()) {
    field = "data";
  } else {
    field = "trade_list";
  }

  if (!(order_data.HasMember("result") && order_data["result"].HasMember(field.c_str()) &&
        order_data["result"][field.c_str()].IsArray())) {
    LOG(ERROR) << "[ParseRestFillInfoList] Unexpected rest fill list format! "
               << JsonToString(order_data);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : order_data["result"][field.c_str()].GetArray()) {
    auto* fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(data["symbol"].GetString());
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(data["order_id"].GetString());
    fill->set_proc_order_id(GetProcOrderId(data));
    fill->set_price(stod(data["exec_price"].GetString()));
    fill->set_qty(stod(data["exec_qty"].GetString()));
    fill->set_fill_id(data["exec_id"].GetString());
    fill->set_fee(stod(data["exec_fee"].GetString()));
    std::string_view fill_type(data["last_liquidity_ind"].GetString());
    if (fill_type == "AddedLiquidity") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (fill_type == "RemovedLiquidity") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
    product_fill_bundle_found = true;
  }
  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

ProductFillBundle BybitFuturesParser::ParseWsFillInfoListV2(const JsonValue& order_data) {
  /*
  {
    "topic": "execution",
    "data": [
        {
            "symbol": "BTCUSD",
            "side": "Buy",
            "order_id": "xxxxxxxx-xxxx-xxxx-9a8f-4a973eb5c418",
            "exec_id": "xxxxxxxx-xxxx-xxxx-8b66-c3d2fcd352f6",
            "order_link_id": "",
            "price": "8300",
            "order_qty": 1,
            "exec_type": "Trade",
            "exec_qty": 1,
            "exec_fee": "0.00000009",
            "leaves_qty": 0,
            "is_maker": false,
            "trade_time": "2020-01-14T14:07:23.629Z" // trade time
        }
    ]
  }
  */
  ProductFillBundle product_fill_bundle;
  if (!order_data.IsObject() || IsBybitFuturesErrMessage(order_data)) {
    LOG(ERROR) << "[ParseWsFillInfoList] " << JsonToString(order_data);
    return product_fill_bundle;
  }

  if (!(order_data.HasMember("data") && order_data["data"].IsArray())) {
    LOG(ERROR) << "Unexpected ws fill list format! " << JsonToString(order_data);
    return product_fill_bundle;
  }

  for (const auto& data : order_data["data"].GetArray()) {
    auto* fill = product_fill_bundle.add_each_fill();
    fill->set_symbol(data["symbol"].GetString());
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(data["order_id"].GetString());
    fill->set_proc_order_id(GetProcOrderId(data));
    fill->set_price(stod(data["price"].GetString()));  // TODO(linchuan) : no exec_price
    fill->set_qty(stod(data["exec_qty"].GetString()));
    fill->set_fill_id(data["exec_id"].GetString());
    fill->set_fee(stod(data["exec_fee"].GetString()));
    if (data["is_maker"].GetBool()) {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    }
  }
  return product_fill_bundle;
}

void BybitFuturesParser::ParseSubmitResponse() {
  /*
  {
    "ret_code": 0,
    "ret_msg": "OK",
    "ext_code": "",
    "ext_info": "",
    "result": {
        "user_id": 1,
        "order_id": "335fd977-e5a5-4781-b6d0-c772d5bfb95b",
        "symbol": "BTCUSD",
        "side": "Buy",
        "order_type": "Limit",
        "price": 8800,
        "qty": 1,
        "time_in_force": "GoodTillCancel",
        "order_status": "Created",
        "last_exec_time": 0,
        "last_exec_price": 0,
        "leaves_qty": 1,
        "cum_exec_qty": 0,
        "cum_exec_value": 0,
        "cum_exec_fee": 0,
        "reject_reason": "",
        "order_link_id": "",
        "created_at": "2019-11-30T11:03:43.452Z",
        "updated_at": "2019-11-30T11:03:43.455Z"
    },
    "time_now": "1575111823.458705",
    "rate_limit_status": 98,
    "rate_limit_reset_ms": 1580885703683,
    "rate_limit": 100
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.base().result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[BybitFuturesSubmitOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[BybitFuturesSubmitOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[BybitFuturesSubmitOrder] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (IsBybitFuturesErrMessage(json)) {
    LOG(ERROR) << "[BybitFuturesSubmitOrder] Error Response" << http_context->res;
    const std::string ret_code = json["ret_code"].GetString();
    if (ret_code == "10016") {
      // exchange internal service process timeout, uncertain order status
      result_.Clear();
      result_.set_type(MsgType::IGNORABLE);
    } else {
      resp->set_error_code(GetOrderErrorCodeFromBybitFutures(json["ret_code"]));
      resp->set_exchange_error_code(ret_code);
      resp->set_exchange_error_msg(json["ret_msg"].GetString());
      resp->set_success(false);
    }
    return;
  }

  if (!(json.HasMember("result") && json["result"].HasMember("order_id"))) {
    LOG(ERROR) << "[BybitFuturesSubmitOrder] exchange order id not found\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  resp->set_success(true);
  resp->set_exchange_order_id(json["result"]["order_id"].GetString());
}

void BybitFuturesParser::ParseCancelResponse() {
  /*
  {
    "ret_code": 0,
    "ret_msg": "OK",
    "ext_code": "",
    "ext_info": "",
    "result": {
        "user_id": 1,
        "order_id": "3bd1844f-f3c0-4e10-8c25-10fea03763f6",
        "symbol": "BTCUSD",
        "side": "Buy",
        "order_type": "Limit",
        "price": 8800,
        "qty": 1,
        "time_in_force": "GoodTillCancel",
        "order_status": "New",
        "last_exec_time": 0,
        "last_exec_price": 0,
        "leaves_qty": 1,
        "cum_exec_qty": 0,
        "cum_exec_value": 0,
        "cum_exec_fee": 0,
        "reject_reason": "",
        "order_link_id": "",
        "created_at": "2019-11-30T11:17:18.396Z",
        "updated_at": "2019-11-30T11:18:01.811Z"
    },
    "time_now": "1575112681.814760",
    "rate_limit_status": 98,
    "rate_limit_reset_ms": 1580885703683,
    "rate_limit": 100
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.base().result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[BybitFuturesCancelOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[BybitFuturesCancelOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[BybitFuturesCancelOrder] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (IsBybitFuturesErrMessage(json)) {
    LOG(ERROR) << "[BybitFuturesCancelOrder] Error Response" << http_context->res;
    const std::string ret_code = json["ret_code"].GetString();
    if (ret_code == "10016") {
      // exchange internal service process timeout, uncertain order status
      result_.Clear();
      result_.set_type(MsgType::IGNORABLE);
    } else {
      resp->set_error_code(GetOrderErrorCodeFromBybitFutures(json["ret_code"]));
      resp->set_exchange_error_code(ret_code);
      resp->set_exchange_error_msg(json["ret_msg"].GetString());
      resp->set_success(false);
    }
    return;
  }

  if (json.HasMember("result") && json["result"].HasMember("order_id")) {
    resp->set_exchange_order_id(std::string(json["result"]["order_id"].GetString()));
  }

  resp->set_success(true);
  resp->set_cancel_confirmed(true);
}

// 2020-6-4: receive balance doubled, hold == available, filter
// 2021-1-10: receive zero balance when bybit system upgrading, filter
// 20220825(daniel): in case it's due to transfer, ignore the abnormal balance update only in 2 mins
bool BybitFuturesParser::CompareLastBalance(
    const std::string& symbol,
    double total_balance,
    const JsonValue& doc) {
  if (last_total_balance_.find(symbol) == last_total_balance_.end()) {
    last_total_balance_.insert(std::make_pair(symbol, total_balance));
    return true;
  }
  if (total_balance > last_total_balance_[symbol] * 1.5 ||
      total_balance < last_total_balance_[symbol] * 0.5) {
    auto now = GetCurrentTimestamp();
    if (abnormal_balance_.count(symbol) == 0) {
      abnormal_balance_[symbol] = now;
      LOG(ERROR)
          << " the balance of " << symbol
          << " suddenly change a lot! prev: " << last_total_balance_[symbol]
          << ", curr: " << total_balance
          << ", this change is ignored and it will be updated after 2 mins if it consistents\n"
          << JsonToString(doc);
    }
    if (now < abnormal_balance_.at(symbol) + 120'000'000'000LL) {
      return false;
    }
  }
  if (abnormal_balance_.count(symbol) > 0) {
    abnormal_balance_.erase(symbol);
  }
  last_total_balance_[symbol] = total_balance;

  return true;
}

AccountPosition BybitFuturesParser::ParseAccountPositionFromWs(const JsonValue& doc) {
  if (!is_account_position_initialized_) {
    return {};
  }
  if (is_linear()) {
    return ParseLinearPositionFromWs(doc);
  } else {
    return ParseNormalPositionFromWs(doc);
  }
}

void BybitFuturesParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.HasMember("topic") &&
      std::string_view(document["topic"].GetString()) == "position") {
    // daniel(20210830): ignored ws balance update due to it's complicated to
    // identifty that if a symbol is inverse nor not
    // if (is_account_balance_initialized_) {
    //   auto account_balance = ParseBalanceFromWs(document);
    //   if (account_balance.has_exchange() && account_balance.has_market_type()) {
    //     result_.set_type(MsgType::ACCOUNT_BALANCE);
    //     *result_.mutable_account_balance() = std::move(account_balance);
    //   }
    // }
    AccountPosition account_position = ParseAccountPositionFromWs(document);
    if (account_position.has_exchange() && account_position.has_market_type()) {
      result_.set_type(MsgType::ACCOUNT_POSITION);
      *result_.mutable_account_position() = std::move(account_position);
    }
  } else if (IsOrderUpdate(document)) {
    std::string topic = document["topic"].GetString();
    if (topic == "order") {
      *result_.mutable_order_list() = ParseWsOrderInfoListV2(document);
      result_.set_type(MsgType::ORDER_STATUS);
    } else if (topic == "execution") {
      *result_.mutable_fill_list() = ParseWsFillInfoListV2(document);
      result_.set_type(MsgType::ORDER_FILL);
    }
  }
}

auto BybitFuturesParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg.type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseRestOrdersInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillInfoList();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseRestAccountBalance();
      break;
    case OrderSubTopicId::REST_ACCOUNT_POSITION:
      ParseRestAccountPosition();
      break;
    default: {
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::bybit_futures::order_v2
